package org.hackystat.ui.projectviewer.gwt.client.dataprovider;

import java.util.List;

import org.hackystat.ui.projectviewer.gwt.client.common.Session;
import org.hackystat.ui.projectviewer.gwt.client.common.types.ProjectData;
import org.hackystat.ui.projectviewer.gwt.client.common.types.ProjectDevSummary;
import org.hackystat.ui.projectviewer.gwt.client.common.types.ProjectSummaryData;
import org.hackystat.ui.projectviewer.gwt.client.common.types.ProjectViewerProperties;
import org.hackystat.ui.projectviewer.gwt.client.dynatable.DynaTableDataProvider.RowDataAcceptor;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;

/**
 * Mediates interactions with ProjectViewer RPC.
 *
 * @author Pavel Senin.
 *
 */
public class DataProviderMediator {

  /** {@literal} RESULT_SUCCESS constant for RPC success. */
  public static final String RPC_SUCCESS = "SUCCESS";

  /** The singleton instance of the mediator. */
  private static DataProviderMediator instance;

  /** The service handler. */
  private ProjectViewerDataProviderAsync service;

  /** The Session handler. */
  private final Session session;

  private int lastMaxRows = -1;

  private String lastProjectName = "";

  private String[] lastData;

  private int lastStartRow = -1;

  private Boolean locked = false;

  /**
   * Constructor, creates GWT RPC mechanism.
   */
  private DataProviderMediator() {
    if (null == instance) {
      // (1) Create the client proxy. Note that although you are creating the
      // service interface proper, you cast the result to the asynchronous
      // version of the interface. The cast is always safe because the generated proxy
      // implements the asynchronous interface automatically.
      //
      service = (ProjectViewerDataProviderAsync) GWT.create(ProjectViewerDataProvider.class);

      // (2) Specify the URL at which our service implementation is running.
      // Note that the target URL must reside on the same domain and port from
      // which the host page was served.
      //
      ServiceDefTarget endpoint = (ServiceDefTarget) service;
      String moduleRelativeURL = GWT.getModuleBaseURL() + "dataprovider";
      endpoint.setServiceEntryPoint(moduleRelativeURL);
    }
    this.session = Session.getInstance();
  }

  /**
   * Reports the instance of the data provider mediator.
   *
   * @return The instance of the data provider mediator.
   */
  public static DataProviderMediator getInstance() {
    if (null == instance) {
      instance = new DataProviderMediator();
    }
    return instance;
  }

  /**
   * Reports sensorbase host.
   *
   * @param callback The GWT thingy.
   */
  public void getProjectViewerProperties(AsyncCallback<ProjectViewerProperties> callback) {
    service.getProjectViewerProperties(callback);
  }

  /**
   * Reports project data.
   *
   * @param projectId The project name.
   * @param callback The GWT thingy.
   */
  public void getProjectData(String projectId, AsyncCallback<ProjectData> callback) {
    service.getProjectData(session.getSensorbaseHost(), session.getUser(), session.getPassword(),
        projectId, callback);
  }

  /**
   * Confirms sensorbase host.
   *
   * @param host The sensorbase URL.
   * @param callback The GWT thingy.
   */
  public void confirmSensorbaseHost(String host, AsyncCallback<Boolean> callback) {
    service.confirmSensorbaseHost(host, callback);
  }

  /**
   * Confirms dpd host.
   *
   * @param host The sensorbase URL.
   * @param callback The GWT thingy.
   */
  public void confirmDPDHost(String host, AsyncCallback<Boolean> callback) {
    service.confirmDPDHost(host, callback);
  }

  /**
   * Confirms telemetry host.
   *
   * @param host The telemetry URL.
   * @param callback The GWT thingy.
   */
  public void confirmTelemetryHost(String host, AsyncCallback<Boolean> callback) {
    service.confirmTelemetryHost(host, callback);
  }

  /**
   * Confirms user.
   *
   * @param host The sensorbase host.
   * @param user The user e-mail.
   * @param pass The user password.
   * @param callback The GWT thingy.
   */
  public void confirmUser(String host, String user, String pass, AsyncCallback<Boolean> callback) {
    service.confirmUser(host, user, pass, callback);
  }

  /**
   * Reports project index for the user.
   *
   * @param host The sensorbase host.
   * @param user The user e-mail.
   * @param pass The user password.
   * @param callback The GWT thingy.
   */
  public void getProjectIndex(String host, String user, String pass,
      AsyncCallback<List<String>> callback) {
    service.getProjectIndex(host, user, pass, callback);
  }

  /**
   * Reports all invitations for the user.
   *
   * @param host The sensorbase host.
   * @param user The user e-mail.
   * @param pass The user password.
   * @param callback The GWT thingy.
   */
  public void getInvitationsList(String host, String user, String pass,
      AsyncCallback<List<String>> callback) {
    service.getInvitationsList(host, user, pass, callback);
  }

  /**
   * Saves project data inot sensorbase.
   *
   * @param pd The project data.
   * @param callback The GWT thingy.
   */
  public void saveProjectData(ProjectData pd, AsyncCallback<Boolean> callback) {
    service.putProjectData(session.getSensorbaseHost(), session.getUser(), session.getPassword(),
        pd, callback);
  }

  /**
   * Register user at sensorbase.
   *
   * @param host The sensorbase host.
   * @param newUser The user e-mail.
   * @param callback The GWT thingy.
   */
  public void registerUser(String host, String newUser, AsyncCallback<Boolean> callback) {
    service.registerUser(host, newUser, callback);

  }

  /**
   * Accepts invitation to the project.
   *
   * @param projectId The project name.
   * @param callback The GWT thingy.
   */
  public void acceptInvitation(String projectId, AsyncCallback<Boolean> callback) {
    service.acceptInvitation(session.getSensorbaseHost(), session.getUser(), session.getPassword(),
        projectId, callback);
  }

  /**
   * Declines invitation to join project.
   *
   * @param projectId The project name.
   * @param callback The GWT thingy.
   */
  public void declineInvitation(String projectId, AsyncCallback<Boolean> callback) {
    service.declineInvitation(session.getSensorbaseHost(), session.getUser(),
        session.getPassword(), projectId, callback);
  }

  /**
   * Deletes the project.
   *
   * @param projectId The project name.
   * @param callback The GWT thingy.
   */
  public void deleteProject(String projectId, AsyncCallback<Boolean> callback) {
    service.deleteProject(session.getSensorbaseHost(), session.getUser(), session.getPassword(),
        projectId, callback);

  }

  /**
   * Gets the server-side exception message.
   *
   * @param callback The GWT thingy.
   */
  public void getException(AsyncCallback<String> callback) {
    service.getExceptionMessage(callback);
  }

  /**
   * Gets the project extendeded summary.
   *
   * @param projectId The project name.
   * @param callback The GWT thingy.
   */
  public void getProjectSummaryData(String projectId, AsyncCallback<ProjectSummaryData> callback) {
    service.getProjectSummaryData(session.getSensorbaseHost(), session.getDPDHost(), session
        .getUser(), session.getPassword(), projectId, callback);
  }

  /**
   * Gets the project development time summary.
   *
   * @param projectId The project name.
   * @param owner The project owner.
   * @param callback The GWT thingy.
   */
  public void getProjectDevTimeData(String projectId, String owner,
      AsyncCallback<ProjectDevSummary> callback) {
    service.getProjectDevTimeData(session.getTelemetryHost(), session.getUser(), session
        .getPassword(), projectId, owner, callback);
  }

  /**
   * Fires selected project data download.
   *
   * @param projectName The selected project name.
   * @param sdtType The selected SDT type.
   */
  public void getSDTData(String projectName, String sdtType) {
    if (this.locked) {
      assert true;
    }
    else {
      lock();
      GWT.log("Calling RPC to fire data download for the project: " + projectName + ", SDT type: "
          + sdtType, null);
      // Fetch the data remotely.
      //
      service.fireDataDownload(session.getSensorbaseHost(), session.getUser(), session
          .getPassword(), projectName, sdtType, new AsyncCallback<Boolean>() {

        public void onFailure(Throwable caught) {
          Window.alert("Unknown exception happend during RPC call. \n Please provide "
              + "feedback to hackystat-dev@googlegroups.com. \n Error: " + caught.getMessage());
        }

        public void onSuccess(Boolean result) {
          unlock();
          GWT.log("Success firing data download!", null);
        }
      });
    }
  }

  /**
   * Updates row data in the dynamic table widget.
   *
   * @param startRow The row to start update from.
   * @param maxRows The max num of row to fetch.
   * @param projectName The project name.
   * @param projectOwner The project owner.
   * @param acceptor The row acceptor.
   */
  public void updateRowData(final int startRow, final int maxRows, final String projectName,
      final String projectOwner, final RowDataAcceptor acceptor) {
    // Check the simple cache first.
    //
    if (startRow == lastStartRow && maxRows == lastMaxRows
        && projectName.equalsIgnoreCase(lastProjectName)) {
      // Use the cached batch.
      //
      pushResults(acceptor, startRow, lastData);
      return;
    }
    lock();
    GWT.log("Calling RPC to get data for the " + session.getSensorbaseHost() + ", "
        + session.getUser() + ", " + session.getPassword() + ", project: " + projectName, null);
    // Fetch the data remotely.
    //
    service.getSensorData(session.getSensorbaseHost(), session.getUser(), session.getPassword(),
        startRow, maxRows, projectName, projectOwner, new AsyncCallback<String[]>() {
          public void onFailure(Throwable caught) {
            acceptor.failed(caught);
          }

          public void onSuccess(String[] result) {
            unlock();
            GWT.log("Success! pushing results ", null);
            String[] sData = result;
            lastProjectName = projectName;
            lastStartRow = startRow;
            lastMaxRows = maxRows;
            lastData = sData;
            pushResults(acceptor, startRow, sData);
          }

        });
  }

  /**
   * Pushes results into table.
   *
   * @param acceptor The row data acceptor/validator.
   * @param startRow The start row number.
   * @param data The data itself.
   */
  private void pushResults(RowDataAcceptor acceptor, int startRow, String[] data) {
    String[][] rows = new String[data.length][];
    for (int i = 0, n = rows.length; i < n; i++) {
      String row = data[i];
      rows[i] = row.split("\t");
    }
    acceptor.accept(startRow, rows);
  }

  /**
   * Locks semaphore to lock the object while transaction.
   *
   * @return The current lock state.
   */
  public boolean lock() {
    if (this.locked) {
      return false;
    }
    return true;
  }

  /**
   * Unlocks transaction semaphore.
   *
   */
  public void unlock() {
    this.locked = false;
  }

  /**
   * Reports whether or not RPC DataProvider mediator locked or not.
   *
   * @return The current lock status.
   */
  public boolean isLocked() {
    return this.locked;
  }

}
